package com.ys.fmms.controller;

import com.ys.fmms.service.VideoService;
import com.ys.fmms.util.Result;
import com.ys.fmms.util.ResultCodeEnum;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 这里用两个接口时为了配合前端断点续传才使用了两个接口，并且前端决定了临时切片的名字，
 * 个人觉得更好的方式是前端无论发送多少次,后端只需要通过名字来判断是不是同一个文件即可，然后临时
 * 切片命名是由后端决定所以可以判别当前临时切片文件中有没有上传过该切片，这里控制都是再前端。
 * <p>
 * 另一种方式：
 * 改用DiskFileItemFactory 和 ServletFileUpload 然后调用upload中的parseFile得到切片文件
 * 再然后合并就使用FileUtils中的readFileToByteArray最后用一个字节输出流或缓冲流输出即可
 */

@RestController
@RequestMapping("/fmms/videoUploadController")
@Api(tags = "处理视频文件上传和下载")
public class VideoUploadController {

    @Value("${save-video-path}")
    private String saveFilePath;

    @Autowired
    private VideoService videoService;

    @Operation(summary = "处理视频上传")
    @RequestMapping(value = "/videoUpload", method = RequestMethod.POST)
    public Result videoUpload(@RequestPart("chunk") MultipartFile multipartFile,
                              @RequestParam("fileName") String fileEncodeName
    ) throws IOException {
        String chunkName = fileEncodeName;
        String savePath = saveFilePath + "cache";
        // 定义一个存储缓存切片的文件夹
        File file = new File(savePath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
        // 切片文件的路径名称
        String chunkSavePath = savePath + File.separator + chunkName;
        File file1 = new File(chunkSavePath);
        // 可以使用输出流 也可直接使用transferTo
        OutputStream os = null;
        os = new BufferedOutputStream(new FileOutputStream(file1));
        os.write(multipartFile.getBytes());
        os.flush();
        os.close();
        // multipartFile.transferTo(file1);
        return Result.ok();
    }

    /**
     * 注意在进行切片合并的过程时需要知道每个切片的起始 和 终止
     * 所以不能直接对每个切片直接读取进行随意合并会造成文件打不开
     * 要么通过对切片文件的命名顺序读写合并，要么采用RandomAccessFile进行读写
     */
    @Operation(summary = "切片文件合并")
    @RequestMapping(value = "/mergeChunk", method = RequestMethod.GET)
    public Result mergeChunk(@RequestParam("hash") String hash,
                             @RequestParam("fileSuffix") String fileSuffix,
                             @RequestParam String fileName,
                             HttpServletRequest request) throws IOException {
        // 定义一个新文件名字
        String newFileName = UUID.randomUUID().toString().replace("-", "").toLowerCase()
                .concat(fileSuffix);
        File ultimateFile = new File(saveFilePath, newFileName);
     /*   BufferedOutputStream os = null;
        try {
            os = new BufferedOutputStream(new FileOutputStream(new File(saveFilePath, newFileName)));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }*/
        // 读取指定目录下的缓存切片然后合并输出到指定的位置
        String tempChunkPath = saveFilePath + "cache" + File.separator;
        File file = new File(tempChunkPath);
        File[] files = null;
        if (file.isDirectory()) {
            // 得到当前路径下的所有文件
            files = file.listFiles();
        }
        // 过滤出满足传入的hash的文件 文件命名是按照hash_1.xxx
        // 对每个切片排序后在合并输出 这个是其中一种方法 使用的是FileUtils.readFileToByteArray方式
      /*  List<File> collectFiles = Arrays.stream(files).filter(item -> item.getName().contains(hash))
                .sorted( .sorted(Comparator.comparing(item -> Integer.parseInt(item.getName().split("\\.")[0].split("-")[1]))))
                .collect(Collectors.toList());
        for (File item : collectFiles) {
            try {
                byte[] bytes = FileUtils.readFileToByteArray(item);
                os.write(bytes);
                os.flush();
                item.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }*/
      /*  if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
*/
        // 第二种方式合并切片文件 使用RandomAccessFile 无需手动创建输出流 且不能使用skip因为不知道切片起始字节数
        // 获取所有切片文件列表并且是按照切片顺序排序过的
        List<File> fileList = Arrays.stream(files).filter(item -> item.getName().contains(hash))
                // 这里的正则表示匹配的是点 单个点匹配的是出了回车 换行的任意字符 然后使用split最终得到切片序号
                .sorted(Comparator.comparing(item -> Integer.parseInt(item.getName().split("\\.")[0].split("-")[1])))
                .collect(Collectors.toList());
        // System.out.println(fileList);
        // 这里需要指定文件最终写出位置
        RandomAccessFile writeFile = new RandomAccessFile(ultimateFile, "rw");
        byte[] buffer = new byte[1024];
        for (File chunkFile : fileList) {
            RandomAccessFile readFile = new RandomAccessFile(chunkFile, "r");
            int readCount;
            // 读取文件到缓冲区中，然后在写出
            while ((readCount = readFile.read(buffer)) != -1) {
                writeFile.write(buffer, 0, readCount);
            }
            // 读取完成后关闭本次读操作
            readFile.close();
            // 删除已读取并写出的文件
            chunkFile.delete();
        }
        writeFile.close();

        File cacheFile = new File(saveFilePath.concat("cache"));
        if (isDirectoryEmpty(cacheFile)) {
            cacheFile.delete();
        }

        String videoUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() +
                "/" + "uploadVideo" + "/" + newFileName;
        // 将地址存储数据库中
        videoService.saveVideoInfo(fileName, newFileName, videoUrl);
        Map<String, String> map = new HashMap<>();
        map.put("videoPath", videoUrl);
        return Result.build(map, ResultCodeEnum.SUCCESS);
    }

    private boolean isDirectoryEmpty(File file) {
        return file.listFiles().length == 0 || file.listFiles() == null;
    }
}
